package cn.iocoder.yudao.module.brokerage.user.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.business.basic.dao.repository.LockRedisRepository;
import cn.iocoder.yudao.framework.business.basic.pojo.vo.UserSimpleVO;
import cn.iocoder.yudao.framework.business.basic.service.impl.AbstractService;
import cn.iocoder.yudao.framework.common.exception.Assertions;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.framework.util.TransactionUtils;
import cn.iocoder.yudao.module.api.brokerage.level.enums.BrokerageLevelTypeEnum;
import cn.iocoder.yudao.module.api.brokerage.record.enums.BrokerageRecordStatusEnum;
import cn.iocoder.yudao.module.api.brokerage.user.enums.BrokerageLevelWeightEnum;
import cn.iocoder.yudao.module.api.infrastructure.config.ConfigApiV2;
import cn.iocoder.yudao.module.api.infrastructure.config.dto.MemberConfigRespDTO;
import cn.iocoder.yudao.module.api.member.agency.AgencyUserApi;
import cn.iocoder.yudao.module.api.member.assets.MemberDigitalAssetsApi;
import cn.iocoder.yudao.module.api.member.user.MemberUserApi;
import cn.iocoder.yudao.module.api.member.vip.VipUserApi;
import cn.iocoder.yudao.module.api.member.vip.vo.VipUserSummaryVO;
import cn.iocoder.yudao.module.api.pay.bill.PayBillApi;
import cn.iocoder.yudao.module.api.pay.bill.dto.PayBillCreateDTO;
import cn.iocoder.yudao.module.api.pay.bill.enums.BillCategoryEnum;
import cn.iocoder.yudao.module.api.pay.bill.enums.BillTypeEnum;
import cn.iocoder.yudao.module.brokerage.record.dao.BrokerageRecordMapper;
import cn.iocoder.yudao.module.brokerage.record.pojo.bo.BrokerageRecordMonthSummaryBO;
import cn.iocoder.yudao.module.brokerage.user.convert.BrokerageUserConvert;
import cn.iocoder.yudao.module.brokerage.user.dao.BrokerageUserMapper;
import cn.iocoder.yudao.module.brokerage.user.mq.BrokerageUserProducer;
import cn.iocoder.yudao.module.brokerage.user.pojo.bo.BrokerageInviteMonthSummaryBO;
import cn.iocoder.yudao.module.brokerage.user.pojo.bo.BrokerageUserMonthSummaryBO;
import cn.iocoder.yudao.module.brokerage.user.pojo.po.BrokerageUserDO;
import cn.iocoder.yudao.module.brokerage.user.pojo.vo.AppBrokerageUserSummaryRespVO;
import cn.iocoder.yudao.module.brokerage.user.pojo.vo.AppBrokerageUserVO;
import jakarta.annotation.Nonnull;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import java.time.LocalDate;
import java.util.*;

import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.module.api.infrastructure.ConfigConstants.CONFIG_MEMBER;

/**
 * 推广用户 Service 实现类
 *
 * @author 山野羡民
 */
@Slf4j
@Service
@Validated
public class AppBrokerageUserServiceImpl extends AbstractService<BrokerageUserDO, AppBrokerageUserVO> implements AppBrokerageUserService {
    private static final long TIMEOUT_MILLIS = 10 * DateUtils.SECOND_MILLIS;
    private static final String LOCK_KEY = "xianmin:member:brokerage-user:lock:%d";
    @Resource
    private LockRedisRepository lockRedisRepository;
    @Resource
    private BrokerageUserMapper brokerageUserMapper;
    @Resource
    private BrokerageRecordMapper brokerageRecordMapper;

    @Resource
    private BrokerageUserProducer brokerageUserProducer;

    @Resource
    private MemberUserApi memberUserApi;
    @Resource
    private VipUserApi vipUserApi;
    @Resource
    private AgencyUserApi agencyUserApi;
    @Resource
    private ConfigApiV2 configApiV2;
    @Resource
    private MemberDigitalAssetsApi memberDigitalAssetsApi;
    @Resource
    private PayBillApi payBillApi;

    @Override
    protected BrokerageUserMapper getInjectedMapper() {
        return brokerageUserMapper;
    }

    @Override
    public BrokerageUserDO getOrCreate(Long userId) {
        Assertions.notNull(userId, "未获取到 userId");
        return lockRedisRepository.lock(String.format(LOCK_KEY, userId), TIMEOUT_MILLIS, () -> {
            BrokerageUserDO entity = getInjectedMapper().findByUserId(userId);
            if (entity == null) {
                entity = new BrokerageUserDO();
                entity.setUserId(userId);
                entity.setType(BrokerageLevelTypeEnum.NORMAL.getValue());
                entity.setFatherUserId(0L);
                entity.setGrandpaUserId(0L);
                entity.setRelationPath("0");
                entity.setInviteCount(0);
                entity.setAchievementPrice(0);
                entity.setBonusGrowth(0);
                entity.setBonusMoney(0);
                entity.setLevelWeight(BrokerageLevelWeightEnum.NONE.getValue());
                getInjectedMapper().insert(entity);
                log.debug("[getOrCreate][userId({}) 关联的推广人员信息不存在，自动创建({})]", userId, entity.getId());
            }
            return entity;
        });
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void autoSyncRelation(Long userId, Long fatherUserId) {
        final BrokerageUserDO entity = getOrCreate(userId);
        final BrokerageUserDO father = fatherUserId <= 0 ? null : getOrCreate(fatherUserId);
        String relationPath = father == null ? "" : father.getRelationPath();
        Long grandpaUserId = father == null ? 0 : father.getFatherUserId();
        final BrokerageUserDO grandpa;
        if (grandpaUserId > 0) {
            log.info("[autoSyncRelation][userId({}) 获取到祖级用户信息: grandpaUserId={}]", userId, grandpaUserId);
            grandpa = getOrCreate(grandpaUserId);
        } else {
            grandpa = null;
        }
        if (fatherUserId <= 0) {
            relationPath = "0";
        } else if (!StrUtil.contains(relationPath, "," + fatherUserId)) {
            relationPath = relationPath + "," + fatherUserId;
        }
        int brokerageLevelType;
        if (vipUserApi.validVip(userId, null)) {
            brokerageLevelType = BrokerageLevelTypeEnum.VIP.getValue();
        } else if (agencyUserApi.validAgency(userId, null)) {
            brokerageLevelType = BrokerageLevelTypeEnum.AGENCY.getValue();
        } else {
            brokerageLevelType = BrokerageLevelTypeEnum.NORMAL.getValue();
        }
        BrokerageUserDO updateObj = new BrokerageUserDO();
        updateObj.setId(entity.getId());
        updateObj.setType(brokerageLevelType);
        updateObj.setFatherUserId(fatherUserId);
        updateObj.setGrandpaUserId(grandpaUserId);
        updateObj.setRelationPath(relationPath);
        getInjectedMapper().updateById(updateObj);
        log.debug("[autoSyncRelation][userId({}) 更新推广用户信息: {}]", userId, updateObj);
        TransactionUtils.afterCommit(() -> {
            if (entity.getFatherUserId() == 0 && fatherUserId > 0) {
                brokerageUserProducer.sendUserBindMessage(userId, fatherUserId);
            }
            if (father != null) {
                updateStatisticsAsync(father);
            }
            if (grandpa != null) {
                updateStatisticsAsync(grandpa);
            }
            return null;
        });
    }

    @Override
    public AppBrokerageUserVO getDetailByUserId(Long userId) {
        BrokerageUserDO userDO = getOrCreate(userId);
        updateStatisticsAsync(userDO);
        return assignUser(userDO);
    }

    @Nonnull
    private AppBrokerageUserVO assignUser(BrokerageUserDO userDO) {
        AppBrokerageUserVO respVO = BrokerageUserConvert.INSTANCE.convert(userDO);
        Set<Long> userIds = new HashSet<>();
        userIds.add(respVO.getUserId());
        userIds.add(respVO.getFatherUserId());
        userIds.add(respVO.getGrandpaUserId());
        userIds.removeIf(it -> it == null || it <= 0L);
        Map<Long, UserSimpleVO> userMap = memberUserApi.getUserSimpleMap(userIds);
        respVO.setUser(userMap.get(respVO.getUserId()));
        respVO.setFatherUser(userMap.get(respVO.getFatherUserId()));
        respVO.setGrandpaUser(userMap.get(respVO.getGrandpaUserId()));
        return respVO;
    }

    @Override
    public AppBrokerageUserSummaryRespVO getSummaryByUserId(Long userId) {
        AppBrokerageUserSummaryRespVO respVO = new AppBrokerageUserSummaryRespVO();
        respVO.setDescendantCount(brokerageUserMapper.selectDescendantCount(userId));
        respVO.setFatherCount(brokerageUserMapper.selectFatherCount(userId));
        respVO.setGrandpaCount(brokerageUserMapper.selectGrandpaCount(userId));
        return respVO;
    }

    @Override
    public BrokerageUserMonthSummaryBO getSummaryByUserIdAndMonth(Long userId, Integer year, Integer month) {
        BrokerageUserDO entity = getOrCreate(userId);
        BrokerageUserMonthSummaryBO summaryBO = new BrokerageUserMonthSummaryBO();
        summaryBO.setYear(year);
        summaryBO.setMonth(month);
        summaryBO.setInviteCount(entity.getInviteCount());
        summaryBO.setTotalMoney(entity.getBonusMoney());
        summaryBO.setTotalGrowth(entity.getBonusGrowth());
        summaryBO.setType(entity.getType());
        summaryBO.setLevelWeight(entity.getLevelWeight());
        int totalUnfreezeMoney = brokerageRecordMapper.selectMoneySum(userId, year, month, BrokerageRecordStatusEnum.UNFREEZE.getValue());
        int totalFreezingMoney = brokerageRecordMapper.selectMoneySum(userId, year, month, BrokerageRecordStatusEnum.FREEZING.getValue());
        summaryBO.setTotalMoney(totalUnfreezeMoney + totalFreezingMoney);
        summaryBO.setTotalFreezingMoney(totalFreezingMoney);
        return summaryBO;
    }

    @Override
    public List<BrokerageUserMonthSummaryBO> getByUserIdAndLastHalfYear(Long userId) {
        List<BrokerageRecordMonthSummaryBO> recordMonthSummaryBOS = brokerageRecordMapper.selectMoneySums(userId, BrokerageRecordStatusEnum.UNFREEZE.getValue(), 7);
        if (CollUtil.isEmpty(recordMonthSummaryBOS)) {
            return Collections.emptyList();
        }
        BrokerageUserDO entity = getOrCreate(userId);
        List<BrokerageUserMonthSummaryBO> list = new ArrayList<>();
        for (BrokerageRecordMonthSummaryBO item : recordMonthSummaryBOS) {
            if (item == null) {
                continue;
            }
            if (item.getYear() == LocalDate.now().getYear() && item.getMonth() == LocalDate.now().getMonth().getValue()) {
                // 排除本月的
                continue;
            }
            BrokerageUserMonthSummaryBO summaryBO = new BrokerageUserMonthSummaryBO();
            summaryBO.setYear(item.getYear());
            summaryBO.setMonth(item.getMonth());
            summaryBO.setInviteCount(0);
            summaryBO.setTotalGrowth(0); // TODO 定时任务按月统计贡献值
            summaryBO.setTotalMoney(item.getTotalMoney());
            summaryBO.setType(entity.getType());// 需定时任务排查类型值（会员是否失效、招商人员是否过期）
            summaryBO.setLevelWeight(entity.getLevelWeight());// 需定时任务按月统计贡献值，根据贡献值计算得来
            list.add(summaryBO);
        }
        List<BrokerageInviteMonthSummaryBO> inviteMonthSummaryBOS = brokerageUserMapper.selectInviteSums(userId, 7);
        if (CollUtil.isEmpty(inviteMonthSummaryBOS)) {
            return list;
        }
        for (BrokerageUserMonthSummaryBO summaryBO : list) {
            for (BrokerageInviteMonthSummaryBO inviteMonthSummaryBO : inviteMonthSummaryBOS) {
                if (Objects.equals(summaryBO.getYear(), inviteMonthSummaryBO.getYear()) && Objects.equals(summaryBO.getMonth(), inviteMonthSummaryBO.getMonth())) {
                    summaryBO.setInviteCount(inviteMonthSummaryBO.getInviteCount());
                    break;
                }
            }
        }
        return list;
    }

    @Override
    public PageResult<AppBrokerageUserVO> getFatherAndGrandpaPage(Integer current, Integer size, Long userId) {
        PageResult<BrokerageUserDO> pageResult = brokerageUserMapper.selectFatherAndGrandpaPage(current, size, userId);
        return assignPageResult(pageResult);
    }

    @Override
    public PageResult<AppBrokerageUserVO> getFatherPage(Integer current, Integer size, Long userId) {
        PageResult<BrokerageUserDO> pageResult = brokerageUserMapper.selectFatherPage(current, size, userId);
        return assignPageResult(pageResult);
    }

    @Override
    public PageResult<AppBrokerageUserVO> getGrandpaPage(Integer current, Integer size, Long userId) {
        PageResult<BrokerageUserDO> pageResult = brokerageUserMapper.selectGrandpaPage(current, size, userId);
        return assignPageResult(pageResult);
    }

    @Override
    public PageResult<AppBrokerageUserVO> getAgencyTeamPage(Integer current, Integer size, Long userId) {
        PageResult<BrokerageUserDO> pageResult = brokerageUserMapper.selectAgencyTeamPage(current, size, userId);
        return assignPageResult(pageResult);
    }

    @Override
    public PageResult<AppBrokerageUserVO> getVipPage(Integer current, Integer size, Long userId, Boolean valid) {
        PageResult<BrokerageUserDO> pageResult = brokerageUserMapper.selectVipPage(current, size, userId, valid);
        return assignPageResult(pageResult);
    }

    @Override
    public List<AppBrokerageUserVO> getFatherListByMonth(Long userId, Integer year, Integer month) {
        List<BrokerageUserDO> list = brokerageUserMapper.selectByUserIdAndMonth(userId, year, month);
        List<AppBrokerageUserVO> result = BrokerageUserConvert.INSTANCE.convertList(list);
        assignList(result);
        return result;
    }

    @Override
    public void updateWhenBindSuccess(Long userId, Long fatherUserId) {
        awardCryptocurrencyAsync(userId, fatherUserId);
    }

    @Override
    public void updateLevelWeight(Long userId, Integer type, Integer level) {
        BrokerageUserDO entity = getOrCreate(userId);
        if (entity != null) {
            BrokerageUserDO updateObj = new BrokerageUserDO();
            updateObj.setId(entity.getId());
            updateObj.setType(type);
            updateObj.setLevelWeight(level);
            getInjectedMapper().updateById(updateObj);
        }
    }

    @Override
    public int getFatherCount(Long userId) {
        return brokerageUserMapper.selectFatherCount(userId);
    }

    @Override
    public int getGrandpaCount(Long userId) {
        return brokerageUserMapper.selectGrandpaCount(userId);
    }

    @Override
    public VipUserSummaryVO getVipSummary(Long userId) {
        return brokerageUserMapper.selectVipSummary(userId);
    }

    @Async // 异步执行，无需同步阻塞
    public void updateStatisticsAsync(BrokerageUserDO entity) {
        log.debug("[updateStatisticsAsync] 用户({}) 更新邀请人数统计信息", entity.getUserId());
        BrokerageUserDO updateObj = new BrokerageUserDO();
        updateObj.setId(entity.getId());
        updateObj.setInviteCount(brokerageUserMapper.selectFatherCount(entity.getUserId()));
        getInjectedMapper().updateById(updateObj);
    }

    @Async // 异步执行，无需同步阻塞
    public void awardCryptocurrencyAsync(Long userId, Long fatherUserId) {
        if (!vipUserApi.validVip(fatherUserId, null)) {
            log.info("[awardCryptocurrencyAsync] 用户({})邀请新用户({}) 上级用户不是会员，不奖励联盟豆", fatherUserId, userId);
            return;
        }
        MemberConfigRespDTO configRespDTO = configApiV2.getConfig(CONFIG_MEMBER, MemberConfigRespDTO.class);
        double giveCryptocurrency = configRespDTO.getInviteGiveCryptocurrency();
        if (giveCryptocurrency <= 0) {
            log.debug("[awardCryptocurrencyAsync] 用户({})邀请新用户({}) 系统配置赠送的联盟豆数量({})无效，不奖励联盟豆", fatherUserId, userId, giveCryptocurrency);
            return;
        }
        // 幂等性校验，就算多次回调，邀请同一个用户也只奖励一次
        if (payBillApi.exists(fatherUserId, String.valueOf(userId))) {
            log.debug("[awardCryptocurrencyAsync] ：用户({})邀请新用户({}) 上级用户奖励已存在，不奖励联盟豆", fatherUserId, userId);
            return;
        }
        UserSimpleVO user = memberUserApi.getUser(userId);
        Assertions.notNull(user, "邀请的新用户(%s)不存在".formatted(userId));
        memberDigitalAssetsApi.changeCryptocurrencyOrigin(fatherUserId, giveCryptocurrency);
        payBillApi.incomeAsync(new PayBillCreateDTO()
                .setUserId(fatherUserId)
                .setCategory(BillCategoryEnum.CRYPTOCURRENCY_ORIGIN)
                .setType(BillTypeEnum.MEMBER_REGISTER_BY_INVITE)
                .setNumber(giveCryptocurrency)
                .setMark("邀请新用户【%s】赠送联盟豆".formatted(user.getDisplayName()))
                .setRelateId(String.valueOf(userId))
                .setExtendField(""));
    }

    @Nonnull
    private PageResult<AppBrokerageUserVO> assignPageResult(PageResult<BrokerageUserDO> pageResult) {
        PageResult<AppBrokerageUserVO> result = BrokerageUserConvert.INSTANCE.convertPage(pageResult);
        assignList(result.getList());
        return result;
    }

    private void assignList(List<AppBrokerageUserVO> list) {
        Set<Long> userIds = new HashSet<>();
        userIds.addAll(convertSet(list, AppBrokerageUserVO::getUserId));
        userIds.addAll(convertSet(list, AppBrokerageUserVO::getFatherUserId));
        userIds.addAll(convertSet(list, AppBrokerageUserVO::getGrandpaUserId));
        userIds.removeIf(it -> it == null || it <= 0L);
        Map<Long, UserSimpleVO> userMap = memberUserApi.getUserSimpleMap(userIds);
        for (AppBrokerageUserVO item : list) {
            item.setUser(userMap.getOrDefault(item.getUserId(), UserSimpleVO.ANONYMOUS));
            item.setFatherUser(userMap.getOrDefault(item.getFatherUserId(), UserSimpleVO.ANONYMOUS));
            item.setGrandpaUser(userMap.getOrDefault(item.getGrandpaUserId(), UserSimpleVO.ANONYMOUS));
        }
    }

}